// Copyright 2015-2023 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "esp32-hal-uart.h"

#if SOC_UART_SUPPORTED
#include "esp32-hal.h"
#include "esp32-hal-periman.h"

#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"

#include "driver/uart.h"
#include "hal/uart_ll.h"
#include "soc/soc_caps.h"
#include "soc/uart_struct.h"
#include "soc/uart_periph.h"
#include "rom/ets_sys.h"
#include "rom/gpio.h"

#include "driver/gpio.h"
#include "hal/gpio_hal.h"
#include "esp_rom_gpio.h"

static int s_uart_debug_nr = 0;

struct uart_struct_t {

#if !CONFIG_DISABLE_HAL_LOCKS
    SemaphoreHandle_t lock;
#endif

    uint8_t num;
    bool has_peek;
    uint8_t peek_byte;
    QueueHandle_t uart_event_queue;   // export it by some uartGetEventQueue() function
    int8_t _rxPin, _txPin, _ctsPin, _rtsPin; // UART GPIOs
};

#if CONFIG_DISABLE_HAL_LOCKS

#define UART_MUTEX_LOCK()
#define UART_MUTEX_UNLOCK()

static uart_t _uart_bus_array[] = {
    {0, false, 0, NULL, -1, -1, -1, -1},
#if SOC_UART_NUM > 1
    {1, false, 0, NULL, -1, -1, -1, -1},
#endif
#if SOC_UART_NUM > 2
    {2, false, 0, NULL, -1, -1, -1, -1},
#endif
};

#else

#define UART_MUTEX_LOCK()    if(uart->lock != NULL) do {} while (xSemaphoreTake(uart->lock, portMAX_DELAY) != pdPASS)
#define UART_MUTEX_UNLOCK()  if(uart->lock != NULL) xSemaphoreGive(uart->lock)

static uart_t _uart_bus_array[] = {
    {NULL, 0, false, 0, NULL, -1, -1, -1, -1},
#if SOC_UART_NUM > 1
    {NULL, 1, false, 0, NULL, -1, -1, -1, -1},
#endif
#if SOC_UART_NUM > 2
    {NULL, 2, false, 0, NULL, -1, -1, -1, -1},
#endif
};

#endif

// Negative Pin Number will keep it unmodified, thus this function can detach individual pins
// This function will also unset the pins in the Peripheral Manager and set the pin to -1 after detaching
static bool _uartDetachPins(uint8_t uart_num, int8_t rxPin, int8_t txPin, int8_t ctsPin, int8_t rtsPin)
{
    if(uart_num >= SOC_UART_NUM) {
        log_e("Serial number is invalid, please use number from 0 to %u", SOC_UART_NUM - 1);
        return false;
    }
    // get UART information
    uart_t* uart = &_uart_bus_array[uart_num];
    bool retCode = true;
    //log_v("detaching UART%d pins: prev,pin RX(%d,%d) TX(%d,%d) CTS(%d,%d) RTS(%d,%d)", uart_num, 
    //        uart->_rxPin, rxPin, uart->_txPin, txPin, uart->_ctsPin, ctsPin, uart->_rtsPin, rtsPin); vTaskDelay(10);

    // detaches pins and sets Peripheral Manager and UART information
    if (rxPin >= 0 && uart->_rxPin == rxPin && perimanGetPinBusType(rxPin) == ESP32_BUS_TYPE_UART_RX) {
        gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[rxPin], PIN_FUNC_GPIO);
        esp_rom_gpio_connect_in_signal(GPIO_FUNC_IN_LOW, UART_PERIPH_SIGNAL(uart_num, SOC_UART_RX_PIN_IDX), false);
        uart->_rxPin = -1;  // -1 means unassigned/detached
        if (!perimanSetPinBus(rxPin, ESP32_BUS_TYPE_INIT, NULL)) {
            retCode = false;
            log_e("UART%d failed to detach RX pin %d", uart_num, rxPin);
        }
    }
    if (txPin >= 0 && uart->_txPin == txPin && perimanGetPinBusType(txPin) == ESP32_BUS_TYPE_UART_TX) {
        gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[txPin], PIN_FUNC_GPIO);
        esp_rom_gpio_connect_out_signal(txPin, SIG_GPIO_OUT_IDX, false, false);
        uart->_txPin = -1;  // -1 means unassigned/detached
        if (!perimanSetPinBus(txPin, ESP32_BUS_TYPE_INIT, NULL)) {
            retCode = false;
            log_e("UART%d failed to detach TX pin %d", uart_num, txPin);
        }
    }
    if (ctsPin >= 0 && uart->_ctsPin == ctsPin && perimanGetPinBusType(ctsPin) == ESP32_BUS_TYPE_UART_CTS) {
        gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[ctsPin], PIN_FUNC_GPIO);
        esp_rom_gpio_connect_in_signal(GPIO_FUNC_IN_LOW, UART_PERIPH_SIGNAL(uart_num, SOC_UART_CTS_PIN_IDX), false);
        uart->_ctsPin = -1;  // -1 means unassigned/detached
        if (!perimanSetPinBus(ctsPin, ESP32_BUS_TYPE_INIT, NULL)) {
            retCode = false;
            log_e("UART%d failed to detach CTS pin %d", uart_num, ctsPin);
        }
    }
    if (rtsPin >= 0 && uart->_rtsPin == rtsPin && perimanGetPinBusType(rtsPin) == ESP32_BUS_TYPE_UART_RTS) {
        gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[rtsPin], PIN_FUNC_GPIO);
        esp_rom_gpio_connect_out_signal(rtsPin, SIG_GPIO_OUT_IDX, false, false);
        uart->_rtsPin = -1;  // -1 means unassigned/detached
        if (!perimanSetPinBus(rtsPin, ESP32_BUS_TYPE_INIT, NULL)) {
            retCode = false;
            log_e("UART%d failed to detach RTS pin %d", uart_num, rtsPin);
        }
    }
    return retCode;
}

// Peripheral Manager detach callback for each specific UART PIN
static bool _uartDetachBus_RX(void *busptr)
{
  // sanity check - it should never happen
  assert(busptr && "_uartDetachBus_RX bus NULL pointer.");
  uart_t* bus = (uart_t*) busptr;
  return _uartDetachPins(bus->num, bus->_rxPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}

static bool _uartDetachBus_TX(void *busptr)
{
  // sanity check - it should never happen
  assert(busptr && "_uartDetachBus_TX bus NULL pointer.");
  uart_t* bus = (uart_t*) busptr;
  return _uartDetachPins(bus->num, UART_PIN_NO_CHANGE, bus->_txPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}


static bool _uartDetachBus_CTS(void *busptr)
{
  // sanity check - it should never happen
  assert(busptr && "_uartDetachBus_CTS bus NULL pointer.");
  uart_t* bus = (uart_t*) busptr;
  return _uartDetachPins(bus->num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, bus->_ctsPin, UART_PIN_NO_CHANGE);
}

static bool _uartDetachBus_RTS(void *busptr)
{
  // sanity check - it should never happen
  assert(busptr && "_uartDetachBus_RTS bus NULL pointer.");
  uart_t* bus = (uart_t*) busptr;
  return _uartDetachPins(bus->num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, bus->_rtsPin);
}

// Attach function for UART 
// connects the IO Pad, set Paripheral Manager and internal UART structure data
static bool _uartAttachPins(uint8_t uart_num, int8_t rxPin, int8_t txPin, int8_t ctsPin, int8_t rtsPin)
{
    if(uart_num >= SOC_UART_NUM) {
        log_e("Serial number is invalid, please use number from 0 to %u", SOC_UART_NUM - 1);
        return false;
    }
    // get UART information
    uart_t* uart = &_uart_bus_array[uart_num];
    //log_v("attaching UART%d pins: prev,new RX(%d,%d) TX(%d,%d) CTS(%d,%d) RTS(%d,%d)", uart_num, 
    //        uart->_rxPin, rxPin, uart->_txPin, txPin, uart->_ctsPin, ctsPin, uart->_rtsPin, rtsPin); vTaskDelay(10);


    bool retCode = true;
    if (rxPin >= 0) {
        // connect RX Pad
        bool ret = ESP_OK == uart_set_pin(uart->num, UART_PIN_NO_CHANGE, rxPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
        if (ret) {
            ret &= perimanSetPinBus(rxPin, ESP32_BUS_TYPE_UART_RX, (void *)uart);
            if (ret) uart->_rxPin = rxPin;
        }
        if (!ret) {
            log_e("UART%d failed to attach RX pin %d", uart_num, rxPin);
        }
        retCode &= ret;
    }
    if (txPin >= 0) {
        // connect TX Pad
        bool ret = ESP_OK == uart_set_pin(uart->num, txPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
        if (ret) {
            ret &= perimanSetPinBus(txPin, ESP32_BUS_TYPE_UART_TX, (void *)uart);
            if (ret) uart->_txPin = txPin;
        }
        if (!ret) {
            log_e("UART%d failed to attach TX pin %d", uart_num, txPin);
        }
        retCode &= ret;
    }
    if (ctsPin >= 0) {
        // connect CTS Pad
        bool ret = ESP_OK == uart_set_pin(uart->num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, ctsPin);
        if (ret) {
            ret &= perimanSetPinBus(ctsPin, ESP32_BUS_TYPE_UART_CTS, (void *)uart);
            if (ret) uart->_ctsPin = ctsPin;
        }
        if (!ret) {
            log_e("UART%d failed to attach CTS pin %d", uart_num, ctsPin);
        }
        retCode &= ret;
    }
    if (rtsPin >= 0) {
        // connect RTS Pad
        bool ret = ESP_OK == uart_set_pin(uart->num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, rtsPin, UART_PIN_NO_CHANGE);
        if (ret) {
            ret &= perimanSetPinBus(rtsPin, ESP32_BUS_TYPE_UART_RTS, (void *)uart);
            if (ret) uart->_rtsPin = rtsPin;
        }
        if (!ret) {
            log_e("UART%d failed to attach RTS pin %d", uart_num, rtsPin);
        }
        retCode &= ret;
    }
    return retCode;
}

// just helper functions
int8_t uart_get_RxPin(uint8_t uart_num)
{
    return _uart_bus_array[uart_num]._rxPin;
}

int8_t uart_get_TxPin(uint8_t uart_num)
{
    return _uart_bus_array[uart_num]._txPin;
}

void uart_init_PeriMan(void)
{
    // set Peripheral Manager deInit Callback for each UART pin
    perimanSetBusDeinit(ESP32_BUS_TYPE_UART_RX, _uartDetachBus_RX);
    perimanSetBusDeinit(ESP32_BUS_TYPE_UART_TX, _uartDetachBus_TX);
    perimanSetBusDeinit(ESP32_BUS_TYPE_UART_CTS, _uartDetachBus_CTS);
    perimanSetBusDeinit(ESP32_BUS_TYPE_UART_RTS, _uartDetachBus_RTS);
}

// Routines that take care of UART events will be in the HardwareSerial Class code
void uartGetEventQueue(uart_t* uart, QueueHandle_t *q)
{
    // passing back NULL for the Queue pointer when UART is not initialized yet
    *q = NULL;
    if(uart == NULL) {
        return;
    }
    *q = uart->uart_event_queue;
    return;
}

bool uartIsDriverInstalled(uart_t* uart) 
{
    if(uart == NULL) {
        return false;
    }

    if (uart_is_driver_installed(uart->num)) {
        return true;
    }
    return false;
}

// Negative Pin Number will keep it unmodified, thus this function can set individual pins
// When pins are changed, it will detach the previous one
bool uartSetPins(uint8_t uart_num, int8_t rxPin, int8_t txPin, int8_t ctsPin, int8_t rtsPin)
{
    if(uart_num >= SOC_UART_NUM) {
        log_e("Serial number is invalid, please use number from 0 to %u", SOC_UART_NUM - 1);
        return false;
    }
    // get UART information
    uart_t* uart = &_uart_bus_array[uart_num];

    bool retCode = true;
    UART_MUTEX_LOCK();

    //log_v("setting UART%d pins: prev->new RX(%d->%d) TX(%d->%d) CTS(%d->%d) RTS(%d->%d)", uart_num, 
    //        uart->_rxPin, rxPin, uart->_txPin, txPin, uart->_ctsPin, ctsPin, uart->_rtsPin, rtsPin); vTaskDelay(10);

    // First step: detachs all previous UART pins
    bool rxPinChanged = rxPin >= 0 && rxPin != uart->_rxPin;
    if (rxPinChanged) {
        retCode &= _uartDetachPins(uart_num, uart->_rxPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    }
    bool txPinChanged = txPin >= 0 && txPin != uart->_txPin;
    if (txPinChanged) {
        retCode &= _uartDetachPins(uart_num, UART_PIN_NO_CHANGE, uart->_txPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    }
    bool ctsPinChanged = ctsPin >= 0 && ctsPin != uart->_ctsPin;
    if (ctsPinChanged) {
        retCode &= _uartDetachPins(uart_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, uart->_ctsPin, UART_PIN_NO_CHANGE);
    }
    bool rtsPinChanged = rtsPin >= 0 && rtsPin != uart->_rtsPin;
    if (rtsPinChanged) {
        retCode &= _uartDetachPins(uart_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, uart->_rtsPin);
    }

    // Second step: attach all UART new pins
    if (rxPinChanged) {
        retCode &= _uartAttachPins(uart_num, rxPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    }
    if (txPinChanged) {
        retCode &= _uartAttachPins(uart_num, UART_PIN_NO_CHANGE, txPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    }
    if (ctsPinChanged) {
        retCode &= _uartAttachPins(uart->num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, ctsPin, UART_PIN_NO_CHANGE);
    }
    if (rtsPinChanged) {
        retCode &= _uartAttachPins(uart->num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, rtsPin);
    }
    UART_MUTEX_UNLOCK();  

    if (!retCode) {
        log_e("UART%d set pins failed.", uart_num);
    }
    return retCode;
}

// 
bool uartSetHwFlowCtrlMode(uart_t *uart, uart_hw_flowcontrol_t mode, uint8_t threshold) {
    if(uart == NULL) {
        return false;
    }
    // IDF will issue corresponding error message when mode or threshold are wrong and prevent crashing
    // IDF will check (mode > HW_FLOWCTRL_CTS_RTS || threshold >= SOC_UART_FIFO_LEN)
    UART_MUTEX_LOCK();
    bool retCode = (ESP_OK == uart_set_hw_flow_ctrl(uart->num, mode, threshold));
    UART_MUTEX_UNLOCK();  
    return retCode;
}

uart_t* uartBegin(uint8_t uart_nr, uint32_t baudrate, uint32_t config, int8_t rxPin, int8_t txPin, uint16_t rx_buffer_size, uint16_t tx_buffer_size, bool inverted, uint8_t rxfifo_full_thrhd)
{
    if(uart_nr >= SOC_UART_NUM) {
        return NULL;
    }
    uart_t* uart = &_uart_bus_array[uart_nr];

    if (uart_is_driver_installed(uart_nr)) {
        uartEnd(uart_nr);
    }

#if !CONFIG_DISABLE_HAL_LOCKS
    if(uart->lock == NULL) {
        uart->lock = xSemaphoreCreateMutex();
        if(uart->lock == NULL) {
            log_e("HAL LOCK error.");
            return NULL;
        }
    }
#endif
    UART_MUTEX_LOCK();

    uart_config_t uart_config;
    uart_config.data_bits = (config & 0xc) >> 2;
    uart_config.parity = (config & 0x3);
    uart_config.stop_bits = (config & 0x30) >> 4;
    uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
    uart_config.rx_flow_ctrl_thresh = rxfifo_full_thrhd;
    uart_config.baud_rate = baudrate;
    // CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F40M for C2 -- CLK_PLL_F48M for H2 -- CLK_PLL_F80M for C6
    uart_config.source_clk = UART_SCLK_DEFAULT;
    bool retCode = ESP_OK == uart_driver_install(uart_nr, rx_buffer_size, tx_buffer_size, 20, &(uart->uart_event_queue), 0);
    if (retCode) retCode &= ESP_OK == uart_param_config(uart_nr, &uart_config);

    // Is it right or the idea is to swap rx and tx pins? 
    if (retCode && inverted) {
        // invert signal for both Rx and Tx
        retCode &= ESP_OK == uart_set_line_inverse(uart_nr, UART_SIGNAL_TXD_INV | UART_SIGNAL_RXD_INV);    
    }
    
    UART_MUTEX_UNLOCK();
    // uartSetPins detaches previous pins if new ones are used over a previous begin()
    if (retCode) retCode &= uartSetPins(uart_nr, rxPin, txPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);

    if (retCode) uartFlush(uart);
    else {
        uartEnd(uart_nr);
        uart = NULL;
        log_e("UART%d initialization error.", uart->num);
    }

    log_v("UART%d baud(%ld) Mode(%x) rxPin(%d) txPin(%d)", uart_nr, baudrate, config, rxPin, txPin);
    return uart;
}

// This function code is under testing - for now just keep it here
void uartSetFastReading(uart_t* uart)
{
    if(uart == NULL) {
        return;
    }

    UART_MUTEX_LOCK();
    // override default RX IDF Driver Interrupt - no BREAK, PARITY or OVERFLOW
    uart_intr_config_t uart_intr = {
        .intr_enable_mask = UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT,   // only these IRQs - no BREAK, PARITY or OVERFLOW
        .rx_timeout_thresh = 1,
        .txfifo_empty_intr_thresh = 10,
        .rxfifo_full_thresh = 2,
    };

    ESP_ERROR_CHECK(uart_intr_config(uart->num, &uart_intr));
    UART_MUTEX_UNLOCK();
}


bool uartSetRxTimeout(uart_t* uart, uint8_t numSymbTimeout)
{
    if(uart == NULL) {
        return false;
    }

    UART_MUTEX_LOCK();
    bool retCode = (ESP_OK == uart_set_rx_timeout(uart->num, numSymbTimeout));
    UART_MUTEX_UNLOCK();
    return retCode;
}

bool uartSetRxFIFOFull(uart_t* uart, uint8_t numBytesFIFOFull)
{
    if(uart == NULL) {
        return false;
    }

    UART_MUTEX_LOCK();
    bool retCode = (ESP_OK == uart_set_rx_full_threshold(uart->num, numBytesFIFOFull));
    UART_MUTEX_UNLOCK();
    return retCode;
}


void uartEnd(uint8_t uart_num)
{
    if(uart_num >= SOC_UART_NUM) {
        log_e("Serial number is invalid, please use number from 0 to %u", SOC_UART_NUM - 1);
        return;
    }
    // get UART information
    uart_t* uart = &_uart_bus_array[uart_num];
   
    UART_MUTEX_LOCK();
    _uartDetachPins(uart_num, uart->_rxPin, uart->_txPin, uart->_ctsPin, uart->_rtsPin);
    if(uart_is_driver_installed(uart_num)) {
        uart_driver_delete(uart_num);
    }
    UART_MUTEX_UNLOCK();
}


void uartSetRxInvert(uart_t* uart, bool invert)
{
    if (uart == NULL)
        return;
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    // POTENTIAL ISSUE :: original code only set/reset rxd_inv bit 
    // IDF or LL set/reset the whole inv_mask!
    // if (invert)
    //     ESP_ERROR_CHECK(uart_set_line_inverse(uart->num, UART_SIGNAL_RXD_INV));
    // else
    //     ESP_ERROR_CHECK(uart_set_line_inverse(uart->num, UART_SIGNAL_INV_DISABLE));
    
#else
    // this implementation is better over IDF API because it only affects RXD
    // this is supported in ESP32, ESP32-S2 and ESP32-C3
    uart_dev_t *hw = UART_LL_GET_HW(uart->num);
    if (invert)
        hw->conf0.rxd_inv = 1;
    else
        hw->conf0.rxd_inv = 0;
#endif 
}


uint32_t uartAvailable(uart_t* uart)
{

    if(uart == NULL) {
        return 0;
    }

    UART_MUTEX_LOCK();
    size_t available;
    uart_get_buffered_data_len(uart->num, &available);
    if (uart->has_peek) available++;
    UART_MUTEX_UNLOCK();
    return available;
}


uint32_t uartAvailableForWrite(uart_t* uart)
{
    if(uart == NULL) {
        return 0;
    }
    UART_MUTEX_LOCK();
    uint32_t available =  uart_ll_get_txfifo_len(UART_LL_GET_HW(uart->num));  
    size_t txRingBufferAvailable = 0;
    if (ESP_OK == uart_get_tx_buffer_free_size(uart->num, &txRingBufferAvailable)) {
        available += txRingBufferAvailable; 
    }
    UART_MUTEX_UNLOCK();
    return available;
}

size_t uartReadBytes(uart_t* uart, uint8_t *buffer, size_t size, uint32_t timeout_ms)
{
    if(uart == NULL || size == 0 || buffer == NULL) {
        return 0;
    }

    size_t bytes_read = 0;

    UART_MUTEX_LOCK();

    if (uart->has_peek) {
        uart->has_peek = false;
        *buffer++ = uart->peek_byte;
        size--;
        bytes_read = 1;
    }

    if (size > 0) {
       int len = uart_read_bytes(uart->num, buffer, size, pdMS_TO_TICKS(timeout_ms));
       if (len < 0) len = 0;  // error reading UART
       bytes_read += len;
    }

        
    UART_MUTEX_UNLOCK();
    return bytes_read;
}

// DEPRICATED but the original code will be kepts here as future reference when a final solution
// to the UART driver is defined in the use case of reading byte by byte from UART.
uint8_t uartRead(uart_t* uart)
{
    if(uart == NULL) {
        return 0;
    }
    uint8_t c = 0;

    UART_MUTEX_LOCK();

    if (uart->has_peek) {
      uart->has_peek = false;
      c = uart->peek_byte;
    } else {

        int len = uart_read_bytes(uart->num, &c, 1, 20 / portTICK_PERIOD_MS);
        if (len <= 0) { // includes negative return from IDF in case of error
            c  = 0;
        }
    }
    UART_MUTEX_UNLOCK();
    return c;
}


uint8_t uartPeek(uart_t* uart)
{
    if(uart == NULL) {
        return 0;
    }
    uint8_t c = 0;

    UART_MUTEX_LOCK();

    if (uart->has_peek) {
      c = uart->peek_byte;
    } else {
        int len = uart_read_bytes(uart->num, &c, 1, 20 / portTICK_PERIOD_MS);
        if (len <= 0) { // includes negative return from IDF in case of error
            c  = 0;
        } else {
            uart->has_peek = true;
            uart->peek_byte = c;
        }
    }
    UART_MUTEX_UNLOCK();
    return c;
}

void uartWrite(uart_t* uart, uint8_t c)
{
    if(uart == NULL) {
        return;
    }
    UART_MUTEX_LOCK();
    uart_write_bytes(uart->num, &c, 1);
    UART_MUTEX_UNLOCK();
}

void uartWriteBuf(uart_t* uart, const uint8_t * data, size_t len)
{
    if(uart == NULL || data == NULL || !len) {
        return;
    }

    UART_MUTEX_LOCK();
    uart_write_bytes(uart->num, data, len);
    UART_MUTEX_UNLOCK();
}

void uartFlush(uart_t* uart)
{
    uartFlushTxOnly(uart, true);
}

void uartFlushTxOnly(uart_t* uart, bool txOnly)
{
    if(uart == NULL) {
        return;
    }
    
    UART_MUTEX_LOCK();
    while(!uart_ll_is_tx_idle(UART_LL_GET_HW(uart->num)));

    if ( !txOnly ) {
        ESP_ERROR_CHECK(uart_flush_input(uart->num));
    }
    UART_MUTEX_UNLOCK();
}

void uartSetBaudRate(uart_t* uart, uint32_t baud_rate)
{
    if(uart == NULL) {
        return;
    }
    UART_MUTEX_LOCK();
    uint32_t sclk_freq;
    if(uart_get_sclk_freq(UART_SCLK_DEFAULT, &sclk_freq) == ESP_OK){
        uart_ll_set_baudrate(UART_LL_GET_HW(uart->num), baud_rate, sclk_freq);
    }
    UART_MUTEX_UNLOCK();
}

uint32_t uartGetBaudRate(uart_t* uart)
{
    uint32_t baud_rate = 0;
    uint32_t sclk_freq;

    if(uart == NULL) {
        return 0;
    }

    UART_MUTEX_LOCK();
    if(uart_get_sclk_freq(UART_SCLK_DEFAULT, &sclk_freq) == ESP_OK){
        baud_rate = uart_ll_get_baudrate(UART_LL_GET_HW(uart->num), sclk_freq);
    }
    UART_MUTEX_UNLOCK();
    return baud_rate;
}

static void ARDUINO_ISR_ATTR uart0_write_char(char c)
{
    while (uart_ll_get_txfifo_len(&UART0) == 0);
    uart_ll_write_txfifo(&UART0, (const uint8_t *) &c, 1);
}

#if SOC_UART_NUM > 1
static void ARDUINO_ISR_ATTR uart1_write_char(char c)
{
    while (uart_ll_get_txfifo_len(&UART1) == 0);
    uart_ll_write_txfifo(&UART1, (const uint8_t *) &c, 1);
}
#endif

#if SOC_UART_NUM > 2
static void ARDUINO_ISR_ATTR uart2_write_char(char c)
{
    while (uart_ll_get_txfifo_len(&UART2) == 0);
    uart_ll_write_txfifo(&UART2, (const uint8_t *) &c, 1);
}
#endif

void uart_install_putc()
{
    switch(s_uart_debug_nr) {
    case 0:
        ets_install_putc1((void (*)(char)) &uart0_write_char);
        break;
#if SOC_UART_NUM > 1
    case 1:
        ets_install_putc1((void (*)(char)) &uart1_write_char);
        break;
#endif
#if SOC_UART_NUM > 2
    case 2:
        ets_install_putc1((void (*)(char)) &uart2_write_char);
        break;
#endif
    default:
        ets_install_putc1(NULL);
        break;
    }
}

// Routines that take care of UART mode in the HardwareSerial Class code
// used to set UART_MODE_RS485_HALF_DUPLEX auto RTS for TXD for ESP32 chips
bool uartSetMode(uart_t *uart, uart_mode_t mode)
{
    if (uart == NULL || uart->num >= SOC_UART_NUM)
    {
        return false;
    }
    
    UART_MUTEX_LOCK();
    bool retCode = (ESP_OK == uart_set_mode(uart->num, mode));
    UART_MUTEX_UNLOCK();
    return retCode;
}

void uartSetDebug(uart_t* uart)
{
    if(uart == NULL || uart->num >= SOC_UART_NUM) {
        s_uart_debug_nr = -1;
    } else {
        s_uart_debug_nr = uart->num;
    }
    uart_install_putc();
}

int uartGetDebug()
{
    return s_uart_debug_nr;
}

int log_printfv(const char *format, va_list arg)
{
    static char loc_buf[64];
    char * temp = loc_buf;
    uint32_t len;
    va_list copy;
    va_copy(copy, arg);
    len = vsnprintf(NULL, 0, format, copy);
    va_end(copy);
    if(len >= sizeof(loc_buf)){
        temp = (char*)malloc(len+1);
        if(temp == NULL) {
            return 0;
        }
    }
#if !CONFIG_DISABLE_HAL_LOCKS
    if(s_uart_debug_nr != -1 && _uart_bus_array[s_uart_debug_nr].lock){
        xSemaphoreTake(_uart_bus_array[s_uart_debug_nr].lock, portMAX_DELAY);
    }
#endif
    
#if CONFIG_IDF_TARGET_ESP32C3
    vsnprintf(temp, len+1, format, arg);
    ets_printf("%s", temp);
#else
    int wlen = vsnprintf(temp, len+1, format, arg);
    for (int i = 0; i < wlen; i++) {
        ets_write_char_uart(temp[i]);
    }
#endif

#if !CONFIG_DISABLE_HAL_LOCKS
    if(s_uart_debug_nr != -1 && _uart_bus_array[s_uart_debug_nr].lock){
        xSemaphoreGive(_uart_bus_array[s_uart_debug_nr].lock);
    }
#endif
    if(len >= sizeof(loc_buf)){
        free(temp);
    }
    return len;
}

int log_printf(const char *format, ...)
{
    int len;
    va_list arg;
    va_start(arg, format);
    len = log_printfv(format, arg);
    va_end(arg);
    return len;
}


static void log_print_buf_line(const uint8_t *b, size_t len, size_t total_len){
    for(size_t i = 0; i<len; i++){
        log_printf("%s0x%02x,",i?" ":"", b[i]);
    }
    if(total_len > 16){
        for(size_t i = len; i<16; i++){
            log_printf("      ");
        }
        log_printf("    // ");
    } else {
        log_printf(" // ");
    }
    for(size_t i = 0; i<len; i++){
        log_printf("%c",((b[i] >= 0x20) && (b[i] < 0x80))?b[i]:'.');
    }
    log_printf("\n");
}

void log_print_buf(const uint8_t *b, size_t len){
    if(!len || !b){
        return;
    }
    for(size_t i = 0; i<len; i+=16){
        if(len > 16){
            log_printf("/* 0x%04X */ ", i);
        }
        log_print_buf_line(b+i, ((len-i)<16)?(len - i):16, len);
    }
}

/*
 * if enough pulses are detected return the minimum high pulse duration + minimum low pulse duration divided by two. 
 * This equals one bit period. If flag is true the function return inmediately, otherwise it waits for enough pulses.
 */
unsigned long uartBaudrateDetect(uart_t *uart, bool flg)
{
// Baud rate detection only works for ESP32 and ESP32S2
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
    if(uart == NULL) {
        return 0;
    }

    uart_dev_t *hw = UART_LL_GET_HW(uart->num);

    while(hw->rxd_cnt.edge_cnt < 30) { // UART_PULSE_NUM(uart_num)
        if(flg) return 0;
        ets_delay_us(1000);
    }

    UART_MUTEX_LOCK();
    //log_i("lowpulse_min_cnt = %d hightpulse_min_cnt = %d", hw->lowpulse.min_cnt, hw->highpulse.min_cnt);
    unsigned long ret = ((hw->lowpulse.min_cnt + hw->highpulse.min_cnt) >> 1);
    UART_MUTEX_UNLOCK();

    return ret;
#else
    return 0;
#endif
}


/*
 * To start detection of baud rate with the uart the auto_baud.en bit needs to be cleared and set. The bit period is 
 * detected calling uartBadrateDetect(). The raw baudrate is computed using the UART_CLK_FREQ. The raw baudrate is 
 * rounded to the closed real baudrate.
 * 
 * ESP32-C3 reports wrong baud rate detection as shown below:
 * 
 * This will help in a future recall for the C3.
 * Baud Sent:          Baud Read:
 *  300        -->       19536
 * 2400        -->       19536
 * 4800        -->       19536 
 * 9600        -->       28818 
 * 19200       -->       57678
 * 38400       -->       115440
 * 57600       -->       173535
 * 115200      -->       347826
 * 230400      -->       701754
 * 
 * 
*/
void uartStartDetectBaudrate(uart_t *uart) {
    if(uart == NULL) {
        return;
    }

// Baud rate detection only works for ESP32 and ESP32S2
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
    uart_dev_t *hw = UART_LL_GET_HW(uart->num);
    hw->auto_baud.glitch_filt = 0x08;
    hw->auto_baud.en = 0;
    hw->auto_baud.en = 1;
#else
    
    // ESP32-C3 requires further testing
    // Baud rate detection returns wrong values 
   
    log_e("baud rate detection for this SoC is not supported.");
    return;

    // Code bellow for C3 kept for future recall
    //hw->rx_filt.glitch_filt = 0x08;
    //hw->rx_filt.glitch_filt_en = 1;
    //hw->conf0.autobaud_en = 0;
    //hw->conf0.autobaud_en = 1;
#endif
}
 
unsigned long uartDetectBaudrate(uart_t *uart)
{
    if(uart == NULL) {
        return 0;
    }

// Baud rate detection only works for ESP32 and ESP32S2
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2

    static bool uartStateDetectingBaudrate = false;

    if(!uartStateDetectingBaudrate) {
        uartStartDetectBaudrate(uart);
        uartStateDetectingBaudrate = true;
    }

    unsigned long divisor = uartBaudrateDetect(uart, true);
    if (!divisor) {
        return 0;
    }

    uart_dev_t *hw = UART_LL_GET_HW(uart->num);
    hw->auto_baud.en = 0;

    uartStateDetectingBaudrate = false; // Initialize for the next round

    unsigned long baudrate = getApbFrequency() / divisor;
    
    //log_i("APB_FREQ = %d\nraw baudrate detected = %d", getApbFrequency(), baudrate);

    static const unsigned long default_rates[] = {300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 74880, 115200, 230400, 256000, 460800, 921600, 1843200, 3686400};

    size_t i;
    for (i = 1; i < sizeof(default_rates) / sizeof(default_rates[0]) - 1; i++)	// find the nearest real baudrate
    {
        if (baudrate <= default_rates[i])
        {
            if (baudrate - default_rates[i - 1] < default_rates[i] - baudrate) {
                i--;
            }
            break;
        }
    }

    return default_rates[i];
#else
    log_e("baud rate detection this SoC is not supported.");
    return 0;
#endif
}

/*
    These functions are for testing purpose only and can be used in Arduino Sketches
    Those are used in the UART examples
*/

/*
    This is intended to make an internal loopback connection using IOMUX
    The function uart_internal_loopback() shall be used right after Arduino Serial.begin(...)
    This code "replaces" the physical wiring for connecting TX <--> RX in a loopback
*/

// gets the right TX or RX SIGNAL, based on the UART number from gpio_sig_map.h
#if SOC_UART_NUM > 2
  #define UART_TX_SIGNAL(uartNumber) (uartNumber == UART_NUM_0 ? U0TXD_OUT_IDX : (uartNumber == UART_NUM_1 ? U1TXD_OUT_IDX : U2TXD_OUT_IDX))
  #define UART_RX_SIGNAL(uartNumber) (uartNumber == UART_NUM_0 ? U0RXD_IN_IDX : (uartNumber == UART_NUM_1 ? U1RXD_IN_IDX : U2RXD_IN_IDX))
#else
  #define UART_TX_SIGNAL(uartNumber) (uartNumber == UART_NUM_0 ? U0TXD_OUT_IDX : U1TXD_OUT_IDX)
  #define UART_RX_SIGNAL(uartNumber) (uartNumber == UART_NUM_0 ? U0RXD_IN_IDX : U1RXD_IN_IDX)
#endif
/*
   This function internally binds defined UARTs TX signal with defined RX pin of any UART (same or different).
   This creates a loop that lets us receive anything we send on the UART without external wires.
*/
void uart_internal_loopback(uint8_t uartNum, int8_t rxPin)
{
  if (uartNum > SOC_UART_NUM - 1 || !GPIO_IS_VALID_GPIO(rxPin)) return;
  esp_rom_gpio_connect_out_signal(rxPin, UART_TX_SIGNAL(uartNum), false, false);
}

/*
    This is intended to generate BREAK in an UART line
*/

// Forces a BREAK in the line based on SERIAL_8N1 configuration at any baud rate
void uart_send_break(uint8_t uartNum)
{
  uint32_t currentBaudrate = 0;
  uart_get_baudrate(uartNum, &currentBaudrate);
  // calculates 10 bits of breaks in microseconds for baudrates up to 500mbps
  // This is very sensetive timing... it works fine for SERIAL_8N1
  uint32_t breakTime = (uint32_t) (10.0 * (1000000.0 / currentBaudrate));
  uart_set_line_inverse(uartNum, UART_SIGNAL_TXD_INV);
  esp_rom_delay_us(breakTime);
  uart_set_line_inverse(uartNum, UART_SIGNAL_INV_DISABLE);
}

// Sends a buffer and at the end of the stream, it generates BREAK in the line
int uart_send_msg_with_break(uint8_t uartNum, uint8_t *msg, size_t msgSize)
{
  // 12 bits long BREAK for 8N1
  return uart_write_bytes_with_break(uartNum, (const void *)msg, msgSize, 12);
}

#endif /* SOC_UART_SUPPORTED */
